// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FIXConnection.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge.Plugins
{
    #region Using Directives

    using System;
    using System.IO;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Forge.Interfaces;

    using QuickFix;

    #endregion

    /// <summary>
    /// The fix Connection.
    /// </summary>
    public class FIXConnection : Connection, IDisposable
    {
        #region Constants and Fields

        /// <summary>
        /// The application.
        /// </summary>
        private readonly FIXApplication application;

        /// <summary>
        /// The hosts.
        /// </summary>
        private readonly string host;

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(System.Reflection.MethodBase.GetCurrentMethod());

        /// <summary>
        /// The ports.
        /// </summary>
        private readonly int port;

        /// <summary>
        /// The connecting.
        /// </summary>
        private bool connecting;

        /// <summary>
        /// The fixPluginType.
        /// </summary>
        private EFIXPluginType fixPluginType;

        /// <summary>
        /// The fixVersion.
        /// </summary>
        private EFIXVersion fixVersion;

        /// <summary>
        /// The initiator.
        /// </summary>
        private Initiator initiator;

        /// <summary>
        /// The logFactory.
        /// </summary>
        private LogFactory logFactory;

        /// <summary>
        /// The messageFactory.
        /// </summary>
        private DefaultMessageFactory messageFactory;

        /// <summary>
        /// The odbcConnectionString.
        /// </summary>
        private string odbcConnectionString;

        /// <summary>
        /// The senderCompId.
        /// </summary>
        private string senderCompId;

        /// <summary>
        /// The sessionId.
        /// </summary>
        private SessionID sessionId;

        /// <summary>
        /// The settings.
        /// </summary>
        private SessionSettings settings;

        /// <summary>
        /// The storeFactory.
        /// </summary>
        private MessageStoreFactory storeFactory;

        /// <summary>
        /// The targetCompId.
        /// </summary>
        private string targetCompId;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="FIXConnection"/> class.
        /// </summary>
        /// <param name="layer">
        /// The layer owning this Connection.
        /// </param>
        /// <param name="fixVersion">
        /// The fix version spoken.
        /// </param>
        /// <param name="fixPluginType">
        /// The fix plugin pype.
        /// </param>
        /// <param name="app">
        /// The application associated.
        /// </param>
        /// <param name="host">
        /// The host.
        /// </param>
        /// <param name="port">
        /// The port.
        /// </param>
        /// <param name="senderCompId">
        /// The senderCompId.
        /// </param>
        /// <param name="targetCompId">
        /// The targetCompId.
        /// </param>
        /// <param name="odbcConnectionString">
        /// The odbcConnectionString.
        /// </param>
        public FIXConnection(ILayer layer, 
                             EFIXVersion fixVersion, 
                             EFIXPluginType fixPluginType, 
                             FIXApplication app, 
                             string host, 
                             int port, 
                             string senderCompId, 
                             string targetCompId, 
                             string odbcConnectionString)
            : base(layer)
        {
            this.application = app;
            this.port = port;
            this.host = host;
            this.fixVersion = fixVersion;
            this.fixPluginType = fixPluginType;
            this.targetCompId = targetCompId;
            this.senderCompId = senderCompId;
            this.odbcConnectionString = odbcConnectionString;

            this.application.LogonEventCB += this.HandlerLogonEventCB;
            this.application.LogoutEventCB += this.HandlerLogoutEventCB;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets Status.
        /// </summary>
        public override EConnectionStatus ConnectionStatus
        {
            get
            {
                if (this.connecting)
                {
                    return EConnectionStatus.Connecting;
                }

                if (this.initiator != null && this.initiator.isLoggedOn())
                {
                    return EConnectionStatus.Connected;
                }

                return EConnectionStatus.Disconnected;
            }
        }

        /// <summary>
        /// Gets SessionID.
        /// </summary>
        public SessionID SessionID
        {
            get
            {
                return this.sessionId;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The connect.
        /// </summary>
        public override Status Connect()
        {
            // Already connected
            if (this.connecting || this.ConnectionStatus == EConnectionStatus.Connected)
            {
                return Status.Failure(Identity, "Already connecting");
            }

            // We're connecting
            this.RaiseConnectingEvent();

            // Do It
            this.ConnectImpl();

            return Status.Success(Identity);
        }

        /// <summary>
        /// The disconnect.
        /// </summary>
        public override Status Disconnect()
        {
            if (this.ConnectionStatus != EConnectionStatus.Connected)
            {
                return Status.Failure(Identity, "Not connected");
            }

            // We're disconnecting
            this.RaiseDisconnectingEvent();

            // Do It
            this.DisconnectImpl();

            return Status.Success(Identity);
        }

        /// <summary>
        /// The to string.
        /// </summary>
        /// <returns>
        /// An URL like string representation of this Connection
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0}:{1} TargetCompID={2} SenderCompID={3}", this.host, this.port, this.targetCompId, this.senderCompId);
        }

        #endregion

        #region Implemented Interfaces

        #region IDisposable

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
            this.application.LogonEventCB -= this.HandlerLogonEventCB;
            this.application.LogoutEventCB -= this.HandlerLogoutEventCB;

            Devel.AsyncExecute(
                true, 
                delegate
                    {
                        if (this.initiator != null)
                        {
                            this.initiator.stop();
                        }
                    });

            this.initiator = null;
            this.sessionId = null;
            this.logFactory = null;
            this.storeFactory = null;
            this.messageFactory = null;
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The session settings stream.
        /// </summary>
        /// <param name="fixVersion">
        /// The fixVersion.
        /// </param>
        /// <param name="fixPluginType">
        /// The fixPluginType.
        /// </param>
        /// <param name="host">
        /// The host.
        /// </param>
        /// <param name="port">
        /// The port.
        /// </param>
        /// <param name="senderCompId">
        /// The senderCompId.
        /// </param>
        /// <param name="targetCompId">
        /// The targetCompId.
        /// </param>
        /// <param name="odbcConnectionString">
        /// The odbcConnectionString.
        /// </param>
        /// <returns>
        /// A stream containing QuickFIX configuration file (at position 0)
        /// </returns>
        private static Stream SessionSettingsStream(EFIXVersion fixVersion, 
                                                    EFIXPluginType fixPluginType, 
                                                    string host, 
                                                    int port, 
                                                    string senderCompId, 
                                                    string targetCompId, 
                                                    string odbcConnectionString)
        {
            // Write to a string
            var stt = new StringWriter();

            // Write to a stream
            var mst = new MemoryStream();
            var st = new StreamWriter(mst);

            // Make the configuration
            SessionSettingsStreamImpl(new TextWriter[] { st, stt }, fixVersion, fixPluginType, host, port, senderCompId, targetCompId, odbcConnectionString);

            // Replace at start
            mst.Seek(0, SeekOrigin.Begin);

            // This one is for reports
            Stdout.WriteLine(stt.ToString());

            // This one is for setupin
            return mst;
        }

        /// <summary>
        /// The session settings stream impl.
        /// </summary>
        /// <param name="writers">
        /// The writers.
        /// </param>
        /// <param name="fixVersion">
        /// The fixVersion.
        /// </param>
        /// <param name="fixPluginType">
        /// The fixPluginType.
        /// </param>
        /// <param name="host">
        /// The host.
        /// </param>
        /// <param name="port">
        /// The port.
        /// </param>
        /// <param name="senderCompId">
        /// The senderCompId.
        /// </param>
        /// <param name="targetCompId">
        /// The targetCompId.
        /// </param>
        /// <param name="odbcConnectionString">
        /// The odbcConnectionString.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        private static void SessionSettingsStreamImpl(TextWriter[] writers, 
                                                      EFIXVersion fixVersion, 
                                                      EFIXPluginType fixPluginType, 
                                                      string host, 
                                                      int port, 
                                                      string senderCompId, 
                                                      string targetCompId, 
                                                      string odbcConnectionString)
        {
            string fixVersionString;
            switch (fixVersion)
            {
                case EFIXVersion.FIX40:
                    fixVersionString = "FIX.4.0";
                    break;
                case EFIXVersion.FIX41:
                    fixVersionString = "FIX.4.1";
                    break;
                case EFIXVersion.FIX42:
                    fixVersionString = "FIX.4.2";
                    break;
                case EFIXVersion.FIX43:
                    fixVersionString = "FIX.4.3";
                    break;
                case EFIXVersion.FIX44:
                    fixVersionString = "FIX.4.4";
                    break;
                case EFIXVersion.FIX50:
                    fixVersionString = "FIX.5.0";
                    break;
                default:
                    throw new Exception("Unknown FIX version");
            }

            foreach (var st in writers)
            {
                // Default parameters
                st.WriteLine("[DEFAULT]");
                st.WriteLine("StartTime=00:00:00");
                st.WriteLine("EndTime=00:00:00");
                st.WriteLine("HeartBtInt=30");

                // st.WriteLine("ResetOnLogon=Y"); // Issues tag 141, incompatible with MBT (sic!)
                st.WriteLine("ResetOnLogout=Y"); // Issues tag 141, incompatible with MBT (sic!)
                st.WriteLine("ResetOnDisconnect=Y"); // Issues tag 141, incompatible with MBT (sic!)
                st.WriteLine("SendRedundantResendRequests=Y");
                st.WriteLine("ConnectionType=initiator");
                st.WriteLine("ReconnectInterval=10");
                st.WriteLine("ValidateUserDefinedFields=N");

                // Don't reject unawaited tags (enables user to add some fields in messages)
                st.WriteLine("MillisecondsInTimeStamp=N");
                st.WriteLine("SocketNodelay=Y");

                // st.WriteLine("LogonTimeout=3");
                // st.WriteLine("LogoutTimeout=3");
                // Persists messages only for order plugins, feed plugins create to much datas !
                if (fixPluginType == EFIXPluginType.FIXOrderPlugin)
                {
                    st.WriteLine("PersistMessages=Y");
                }
                else if (fixPluginType == EFIXPluginType.FIXFeedPlugin)
                {
                    st.WriteLine("PersistMessages=N");
                }

                // st.WriteLine("OdbcLogUser="); //  User name logging in to ODBC database.  valid user with read/write access to appropriate tables in database  sa  
                // st.WriteLine("OdbcLogPassword=");  // Users password.  correct ODBC password for user  empty password  
                st.WriteLine("OdbcLogConnectionString={0}", odbcConnectionString);

                // ODBC Connection string for database  Valid ODBC Connection string  DATABASE=quickfix;DRIVER={SQL Server};SERVER=(local);  

                // st.WriteLine("OdbcStoreUser="); //  User name logging in to ODBC database.  valid user with read/write access to appropriate tables in database  sa  
                // st.WriteLine("OdbcStorePassword=");  // Users password.  correct ODBC password for user  empty password  
                st.WriteLine("OdbcStoreConnectionString={0}", odbcConnectionString);
                st.WriteLine();

                // Session
                st.WriteLine("[SESSION]");
                st.WriteLine("BeginString={0}", fixVersionString);
                st.WriteLine(@"DataDictionary={0}\Org.OpenTrader.Framework.Forge.Plugins.{1}.xml", Environment.CurrentDirectory, fixVersionString);
                st.WriteLine(string.Format("TargetCompID={0}", targetCompId));
                st.WriteLine(string.Format("SenderCompID={0}", senderCompId));
                st.WriteLine(string.Format("SocketConnectPort={0}", port));
                st.WriteLine(string.Format("SocketConnectHost={0}", host));

                st.WriteLine();
                st.Flush();
            }
        }

        /// <summary>
        /// The connect impl.
        /// </summary>
        private void ConnectImpl()
        {
            Devel.AsyncExecute(
                false, 
                delegate
                    {
                        this.settings =
                            new SessionSettings(
                                SessionSettingsStream(
                                    this.fixVersion, this.fixPluginType, this.host, this.port, this.senderCompId, this.targetCompId, this.odbcConnectionString));
                        this.logFactory = new ScreenLogFactory(true, true, false);
                        this.storeFactory = new OdbcStoreFactory(this.settings);

                        // this.logFactory = new FileLogFactory("c:\\FIX");
                        // this.storeFactory = new FileStoreFactory("C:\\FIX");
                        this.messageFactory = new DefaultMessageFactory();

                        this.initiator = new /*Threaded*/ SocketInitiator(
                            this.application, this.storeFactory, this.settings, this.logFactory, this.messageFactory);
                        this.initiator.start();
                    });
        }

        /// <summary>
        /// The disconnect impl.
        /// </summary>
        private void DisconnectImpl()
        {
            var i = this.initiator;
            Devel.AsyncExecute(
                false, 
                delegate
                    {
                        if (i != null)
                        {
                            i.stop();
                        }
                    });

            this.initiator = null;
            this.connecting = false;
            this.settings = null;
            this.logFactory = null;
            this.storeFactory = null;
            this.messageFactory = null;

            // We're disconnected
            this.RaiseDisconnectedEvent();
        }

        /// <summary>
        /// The handler for logon events.
        /// </summary>
        /// <param name="sender">
        /// The sender ot the event.
        /// </param>
        /// <param name="e">
        /// The contents of the event.
        /// </param>
        private void HandlerLogonEventCB(object sender, SessionIDEventArgs e)
        {
            this.sessionId = e.SessionID;
            this.connecting = false;

            // We're connected
            this.RaiseConnectedEvent();
        }

        /// <summary>
        /// The handler for logout events.
        /// </summary>
        /// <param name="sender">
        /// The sender ot the event.
        /// </param>
        /// <param name="e">
        /// The contents of the event.
        /// </param>
        private void HandlerLogoutEventCB(object sender, SessionIDEventArgs e)
        {
            this.DisconnectImpl();
        }

        #endregion
    }
}